/*
The command tail [ –n num ] file prints the last num lines (ten by default) of the
named file. Implement this command using I/O system calls (lseek(), read(), write(),
and so on). Keep in mind the buffering issues described in this chapter, in order to
make the implementation efficient.
 */

/*
It will be probably better to have malloced buffer instead of reading twice
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <alloca.h>
#include <fcntl.h>

static const size_t buffer_size = 10;

static size_t
parse_argument(int argc, char *argv[])
{
  int opt;
  char *nstr = NULL;
  while ((opt = getopt(argc, argv, ":n:")) != -1)
    {
      switch (opt)
        {
        case 'n': nstr = optarg; break;
        default:
          puts("Usage: tail [ -n num ]");
          exit(EXIT_FAILURE);
          break;
        }
    }
  return nstr ? atoll(nstr) : 5;
}

void
exit_with_error(const char *string)
{
  fprintf(stderr, string);
  exit(EXIT_FAILURE);
}

off_t
find_tail_pos(const char buff[static 1], size_t buff_size,
              size_t *remaining_newline)
{
  off_t offset = -1;
  while (buff_size--)
    {
      if (buff[buff_size] == '\n')
        {
          --*remaining_newline;
        }
      if (!*remaining_newline)
        {
          offset = buff_size + 1;
          break;
        }
    }

  return offset;
}

int
main(int argc, char *argv[])
{
  size_t num = parse_argument(argc, argv);

  int fd, open_flags;
  open_flags = O_RDONLY;
  fd = open(argv[optind], open_flags);
  if (fd == -1) exit_with_error("can not open file");
  off_t offset = lseek(fd, 0, SEEK_END);
  size_t size = buffer_size;

  size_t newline_counter = num + 1; /* +1? last character */
  off_t print_pos = -1;
  while(offset)
    {
      offset -= size;
      if (offset < 0)
        {
          size = offset + size;
          offset = 0;
        }
      lseek(fd, offset, SEEK_SET);
      char buff[size + 1];
      buff[size] = '\0';
      if (read(fd, buff, size) == -1) {
        fprintf(stderr, "offset: %d\n", offset);
        exit_with_error("unable to read file");
      }
      if ((print_pos = find_tail_pos(buff, size, &newline_counter)) != -1)
        break;
    }

  char buff[size + 1];
  buff[size] = '\0';
  print_pos += offset;
  lseek(fd, print_pos, SEEK_SET);
  ssize_t byte_read;
  while ((byte_read = read(fd, buff, size)) > 0)
    {
      if (byte_read == size)
        printf("%s", buff);
      else
        {
          buff[byte_read] = '\0';
          printf("%s", buff);
        }
    }
  close(fd);
  return EXIT_SUCCESS;
}
